Skip to content

Prevent hanging in svn+ssh#1230

Open
spoorcc wants to merge 4 commits into
mainfrom
svn-ssh-non-onteractive
Open

Prevent hanging in svn+ssh#1230
spoorcc wants to merge 4 commits into
mainfrom
svn-ssh-non-onteractive

Conversation

@spoorcc

@spoorcc spoorcc commented May 25, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • SVN+SSH connections now run in non-interactive mode to prevent hanging
  • Bug Fixes

    • SSH host key verification failures are now detected and reported with actionable hints for trusting hosts
  • Documentation

    • Added troubleshooting guidance for SSH remotes and non-interactive operation
  • Tests

    • Expanded tests covering SSH host-key failure handling and diff/export behaviors
  • Chores

    • CI/build and policy config tweaks to improve build and tooling behavior

@coderabbitai

coderabbitai Bot commented May 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@spoorcc, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 41 minutes and 19 seconds. Learn how PR review limits work.

Your organization has run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans include higher PR review limits than trial, open-source, and free plans. In all cases, reviews become available again over time. During sustained high-volume PR review activity, CodeRabbit may temporarily slow when the next review becomes available.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 6f7e7934-7bc6-49d8-889c-bac6c27cf164

📥 Commits

Reviewing files that changed from the base of the PR and between 0cc082e and 5fd2026.

📒 Files selected for processing (7)
  • .readthedocs.yml
  • CHANGELOG.rst
  • dfetch/vcs/svn.py
  • doc/howto/troubleshooting.rst
  • features/check-svn-repo.feature
  • tests/test_svn.py
  • tests/test_svn_vcs.py

Walkthrough

This PR centralizes non-interactive SVN-over-SSH handling: injects BatchMode=yes into SVN_SSH, detects SSH host-key verification failures and raises a dedicated error with ssh-keyscan/ssh -T hints, applies these helpers across SvnRemote and SvnRepo methods, and adds tests, docs, and config updates.

Changes

Non-Interactive SVN SSH Mode

Layer / File(s) Summary
Core SVN non-interactive SSH infrastructure
dfetch/vcs/svn.py
Adds urllib/functools imports, defines SshHostKeyError, and implements _extend_env_for_non_interactive_mode(), _raise_if_ssh_host_key_error(), _run_svn_raw(), and _run_svn() to enforce svn --non-interactive and produce ssh-keyscan/ssh -T remediation hints on host-key failures.
SvnRemote methods using _run_svn
dfetch/vcs/svn.py
Refactors SvnRemote.is_svn(), list_of_branches(), list_of_tags(), browse_tree(), and ls_tree() to call _run_svn/_run_svn_raw; SSH host-key verification failures now propagate as the centralized error while other failures yield the previous empty-list/behavior.
SvnRepo methods using _run_svn
dfetch/vcs/svn.py
Refactors SvnRepo.is_svn(), externals(), externals_from_url(), get_info_from_target(), get_last_changed_revision(), untracked_files(), export(), files_in_path(), ignored_files(), any_changes_or_untracked(), create_diff(), and get_username() to use _run_svn/_run_svn_raw; create_diff() now runs diff --ignore-properties . locally and fetches patch bytes via _run_svn_raw.
Test mocking and success-stub updates
tests/test_svn.py, tests/test_svn_vcs.py
Adds MagicMock import, changes success-case subprocess stubs to MagicMock(stdout=b""), and updates SvnRepo.export() tests to expect --non-interactive before the export subcommand.
SSH host-key error regression tests
tests/test_svn.py
Adds parametrized tests that assert SSH host-key verification stderr triggers RuntimeError with a “known hosts” hint for SvnRemote and multiple SvnRepo methods; tests verify hostname and username@hostname hinting and cover browse-tree and no-URL cases.
Docs and configuration updates
CHANGELOG.rst, .claude/settings.json, .readthedocs.yml, doc/howto/troubleshooting.rst, features/check-svn-repo.feature
Adds CHANGELOG entry about svn+ssh:// non-interactive mode, new troubleshooting subsection on SSH remotes, broadens .claude/settings.json permission globs, adds .readthedocs.yml post_checkout git fetch, and updates feature expectation for svn --non-interactive argument order.

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs:

  • dfetch-org/dfetch#1220: Overlaps changes to dfetch/vcs/svn.py externals handling and externals parsing/reporting.
  • dfetch-org/dfetch#1095: Related changes around SvnRepo.export() argument construction and revision handling.

Suggested labels:
documentation

Suggested reviewers:

  • ben-edna
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 62.79% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Prevent hanging in svn+ssh' directly describes the main change: preventing hanging when using svn+ssh connections by implementing non-interactive mode and SSH host key handling.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch svn-ssh-non-onteractive

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@spoorcc

spoorcc commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@spoorcc

spoorcc commented May 28, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented May 28, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.rst`:
- Line 23: Update the CHANGELOG line that currently reads "* Run ``svn+ssh://``
connections in non-interactive mode to prevent hanging (`#0`)" to reference the
correct issue/PR number; replace the placeholder "(`#0`)" with "(`#1230`)" so the
entry reads "...prevent hanging (`#1230`)". Ensure only the numeric reference is
changed and formatting (backticks and punctuation) is preserved.

In `@dfetch/vcs/svn.py`:
- Around line 37-55: The remediation message currently uses the output of
_ssh_target_from_url (which returns "user@host") for both commands; change
_raise_if_ssh_host_key_error to parse the URL (urlparse(url)) locally, build
host_only = parsed.hostname or url for the ssh-keyscan suggestion, and keep
target = _ssh_target_from_url(url) (user@host) for the ssh -T suggestion so the
ssh-keyscan line uses only the hostname/IP while the ssh -T line still shows
user@host.

In `@example/dfetch.yaml`:
- Line 10: Update the example near the url-base entry (url-base:
svn+ssh://svn.code.sf.net/p/) to add a brief note explaining that SourceForge
requires users to register an SSH public key in their account and to accept the
host's SSH key (add guidance to add the host key to ~/.ssh/known_hosts or run
ssh-keyscan for the host), and include a short smoke-test instruction to verify
the repository is reachable via svn+ssh (e.g., attempt an svn list/ls of the
tortoisesvn/code path) so reviewers know how to validate the ssh-key/known-hosts
setup non-interactively.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a4056c4b-b3e0-4ab1-b83c-6b6a4029179a

📥 Commits

Reviewing files that changed from the base of the PR and between fdf22cc and 16d4f3a.

📒 Files selected for processing (4)
  • CHANGELOG.rst
  • dfetch/vcs/svn.py
  • example/dfetch.yaml
  • tests/test_svn.py

Comment thread CHANGELOG.rst Outdated
Comment thread dfetch/vcs/svn.py Outdated
Comment thread example/dfetch.yaml Outdated
@spoorcc spoorcc force-pushed the svn-ssh-non-onteractive branch from 16d4f3a to 81387c8 Compare June 1, 2026 20:02

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/test_svn.py`:
- Around line 569-583: Add an autouse pytest fixture in this test module that
centralizes clearing the lru_cache for _extend_env_for_non_interactive_mode:
create a fixture (autouse=True) which imports
dfetch.vcs.svn._extend_env_for_non_interactive_mode and calls
_extend_env_for_non_interactive_mode.cache_clear() before each test (and
optionally after) so the cached process-global state is reset; then remove the
repetitive per-test imports and explicit
_extend_env_for_non_interactive_mode.cache_clear() calls from tests like
test_svn_ssh_env_has_batch_mode, test_svn_ssh_env_preserves_existing_batch_mode
and the *_passes_batch_mode_env tests.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 86c26433-10d8-4f73-b002-421224f1f319

📥 Commits

Reviewing files that changed from the base of the PR and between 16d4f3a and 81387c8.

📒 Files selected for processing (3)
  • CHANGELOG.rst
  • dfetch/vcs/svn.py
  • tests/test_svn.py

Comment thread tests/test_svn.py
@spoorcc spoorcc force-pushed the svn-ssh-non-onteractive branch 2 times, most recently from e470253 to d798bd0 Compare June 2, 2026 19:28

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dfetch/project/svnsubproject.py`:
- Around line 165-169: The except block catching SubprocessCommandError should
preserve and log the underlying failure details; change the handler to capture
the exception (e.g., "except SubprocessCommandError as e:") and include the
revision and the exception/return-code information in the logger.warning call
for the externals-report skip (use the existing complete_path and the local
revision variable and e or e.returncode/str(e) as available), or include
exc_info=True to preserve traceback.

In `@dfetch/util/cmdline.py`:
- Around line 49-51: The inline Bandit suppression should be removed and the
root cause fixed in run_on_cmdline: stop relying on "# nosec" by ensuring
subprocess.run is invoked safely (use shell=False and a sequence for cmd or
parse cmd with shlex.split), validate or sanitize the cmd input (or enforce a
whitelist of allowed commands) before calling subprocess.run, and keep existing
arguments (env, capture_output, check, timeout) while removing the "# nosec"
comment next to subprocess.run so Bandit no longer flags an unsafe shell
invocation of cmd.
- Around line 52-58: The TimeoutExpired handler in run_on_cmdline currently
drops captured output; change the except block to extract exc.stdout and
exc.stderr (they may be bytes or None), decode bytes to str using
errors="replace", default to "" when None, and pass those decoded stdout/stderr
into the SubprocessCommandError constructor (retain the timeout message and exit
code 124) so partial output is preserved for diagnostics; reference
run_on_cmdline and SubprocessCommandError to locate and update the handler.

In `@dfetch/vcs/svn.py`:
- Around line 239-261: externals_from_url should handle SSH host-key
verification errors like other SVN helpers: wrap the run_on_cmdline call in
externals_from_url in a try/except that catches SubprocessCommandError and calls
_raise_if_ssh_host_key_error(logger, err) (or re-raise if not an SSH host-key
issue) before proceeding; ensure you still decode result.stdout and call
SvnRepo._normalize_url_prefix and SvnRepo._parse_externals after successful
run_on_cmdline.
- Around line 25-35: The cached function _extend_env_for_non_interactive_mode
currently returns a mutable dict which lru_cache will reuse across calls; change
it to return an immutable mapping so callers cannot mutate the cached object:
keep the caching but wrap the computed env in types.MappingProxyType (import
MappingProxyType from types) and update the return type annotation to
Mapping[str, str] (or similar typing.Mapping) so the cached value is an
immutable view of the environment.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: e6748593-13b3-4b61-a828-c15382bb17b7

📥 Commits

Reviewing files that changed from the base of the PR and between 81387c8 and d798bd0.

📒 Files selected for processing (9)
  • CHANGELOG.rst
  • dfetch/project/svnsubproject.py
  • dfetch/util/cmdline.py
  • dfetch/vcs/svn.py
  • doc/explanation/threat_model_usage.rst
  • security/tm_usage.py
  • tests/test_cmdline.py
  • tests/test_svn.py
  • tests/test_svnsubproject.py

Comment thread dfetch/project/svnsubproject.py Outdated
Comment on lines +165 to +169
except SubprocessCommandError:
logger.warning(
"Could not retrieve SVN externals from '%s' — skipping report.",
complete_path,
)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Preserve the subprocess failure details in this warning.

This warning drops the reason externals reporting was skipped. Please include at least the revision plus the underlying SVN error/return code so operators can tell an expected timeout/host-key case from an unexpected repository/configuration failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dfetch/project/svnsubproject.py` around lines 165 - 169, The except block
catching SubprocessCommandError should preserve and log the underlying failure
details; change the handler to capture the exception (e.g., "except
SubprocessCommandError as e:") and include the revision and the
exception/return-code information in the logger.warning call for the
externals-report skip (use the existing complete_path and the local revision
variable and e or e.returncode/str(e) as available), or include exc_info=True to
preserve traceback.

Comment thread dfetch/util/cmdline.py Outdated
Comment on lines +49 to +51
proc = subprocess.run(
cmd, env=env, capture_output=True, check=True, timeout=timeout
) # nosec

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion | 🟠 Major | ⚡ Quick win

Remove the inline Bandit suppression from the subprocess wrapper.

This change still relies on # nosec in production code. Since run_on_cmdline is already the single subprocess boundary, fix the underlying Bandit concern here once instead of suppressing the call inline.

As per coding guidelines, dfetch/**/*.py: "Avoid inline lint suppressions (# noqa, # type: ignore, # pylint: disable, # pyright: ignore) without fixing the root cause; exception permitted for module-level tool headers (lines 1–5) in test files".

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dfetch/util/cmdline.py` around lines 49 - 51, The inline Bandit suppression
should be removed and the root cause fixed in run_on_cmdline: stop relying on "#
nosec" by ensuring subprocess.run is invoked safely (use shell=False and a
sequence for cmd or parse cmd with shlex.split), validate or sanitize the cmd
input (or enforce a whitelist of allowed commands) before calling
subprocess.run, and keep existing arguments (env, capture_output, check,
timeout) while removing the "# nosec" comment next to subprocess.run so Bandit
no longer flags an unsafe shell invocation of cmd.

Comment thread dfetch/util/cmdline.py Outdated
Comment on lines +52 to +58
except subprocess.TimeoutExpired as exc:
raise SubprocessCommandError(
list(exc.cmd) if isinstance(exc.cmd, (list, tuple)) else [str(exc.cmd)],
"",
f"Command timed out after {exc.timeout:.0f}s",
124,
) from exc

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the target file and nearby context
FILE="dfetch/util/cmdline.py"
echo "== $FILE =="
nl -ba "$FILE" | sed -n '1,140p'
echo

# Locate SubprocessCommandError definition
echo "== SubprocessCommandError definition/usages =="
rg -n "class SubprocessCommandError|SubprocessCommandError\(" -S dfetch/util/cmdline.py dfetch -g'*.py'

# Show the exact SubprocessCommandError class if found
matches=$(rg -n "class SubprocessCommandError" -S dfetch -g'*.py' || true)
if [ -n "$matches" ]; then
  echo
  echo "== Showing class SubprocessCommandError block =="
  # Print file sections around each match
  echo "$matches" | while read -r line; do
    f=$(echo "$line" | cut -d: -f1)
    ln=$(echo "$line" | cut -d: -f2)
    start=$((ln-20))
    end=$((ln+80))
    nl -ba "$f" | sed -n "${start},${end}p"
    echo
  done
fi

# Check whether os is already imported in cmdline.py
echo "== Imports in dfetch/util/cmdline.py =="
rg -n "^import|^from" "$FILE"

Repository: dfetch-org/dfetch

Length of output: 151


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="dfetch/util/cmdline.py"
echo "== File exists?"
test -f "$FILE" && echo "OK: $FILE" || (echo "MISSING: $FILE" && exit 1)

echo
echo "== $FILE (lines 1-140)"
cat -n "$FILE" | sed -n '1,140p'

echo
echo "== TimeoutExpired branch (exact matches)"
rg -n "except subprocess\.TimeoutExpired as exc" -n "$FILE" || true
rg -n "TimeoutExpired" "$FILE" || true

echo
echo "== SubprocessCommandError definition/constructor"
rg -n "class SubprocessCommandError|def __init__\(|SubprocessCommandError\(" -S "$FILE" || true

# Print block around the first class definition match, if present
class_line=$(rg -n "class SubprocessCommandError" "$FILE" | head -n1 | cut -d: -f1 || true)
if [ -n "${class_line:-}" ]; then
  start=$((class_line-20))
  end=$((class_line+120))
  echo
  echo "== Showing around SubprocessCommandError at ~line $class_line"
  cat -n "$FILE" | sed -n "${start},${end}p"
fi

echo
echo "== Imports in $FILE"
rg -n "^(import|from )" "$FILE" || true

echo
echo "== SubprocessCommandError usages elsewhere (show a few)"
rg -n "SubprocessCommandError\(" -S dfetch -g'*.py' | head -n 20

Repository: dfetch-org/dfetch

Length of output: 6117


🌐 Web query:

Python subprocess.TimeoutExpired attributes output stdout stderr capture_output run

💡 Result:

In Python, when using subprocess.run with a timeout parameter, a subprocess.TimeoutExpired exception is raised if the process does not terminate within the specified time [1][2]. Important details regarding this exception and output capture include: 1. Accessing Captured Output: When a TimeoutExpired exception is raised, you can access the output captured up to the point of the timeout via the exception's attributes [3]. Specifically: - stdout: Contains the standard output captured [1][2]. - stderr: Contains the standard error captured [1][2]. - output: An alias for stdout, provided for symmetry with stderr [1][2]. 2. Data Format: Regardless of whether you set text=True or encoding in your subprocess.run call, the stdout and stderr attributes on the TimeoutExpired exception will always be in bytes [1][2][4]. They will be None if no output was captured or if capture_output=True (or stdout=PIPE/stderr=PIPE) was not used [1][2]. 3. Usage Pattern: To access this data, you must catch the exception [3]. For example: try: result = subprocess.run(["your_command"], capture_output=True, text=True, timeout=5) except subprocess.TimeoutExpired as e: # Access bytes output here captured_stdout = e.stdout captured_stderr = e.stderr # If text=True was used, you may need to decode manually: # decoded_stdout = e.stdout.decode if e.stdout else None 4. Behavior with capture_output: When you use capture_output=True in subprocess.run, Python automatically configures stdout and stderr as pipes (stdout=PIPE and stderr=PIPE) [5]. If a timeout occurs, the subprocess library ensures that whatever data was already buffered in these pipes is attached to the exception [1][2]. Note: Before Python 3.5, the stdout and stderr attributes were not present on the TimeoutExpired exception [1][2]. Always ensure your code accounts for these attributes being bytes, even when operating in text mode [6][1].

Citations:


Preserve captured stdout/stderr on subprocess timeouts for better diagnostics.

run_on_cmdline uses subprocess.run(..., capture_output=True), and subprocess.TimeoutExpired can include the buffered stdout/stderr from the timed-out process. The current handler replaces stdout with "" and only keeps the timeout message, dropping the most useful partial output.

File: dfetch/util/cmdline.py
Lines: 52-58

    except subprocess.TimeoutExpired as exc:
        raise SubprocessCommandError(
            list(exc.cmd) if isinstance(exc.cmd, (list, tuple)) else [str(exc.cmd)],
            "",
            f"Command timed out after {exc.timeout:.0f}s",
            124,
        ) from exc
Suggested fix
     except subprocess.TimeoutExpired as exc:
+        stdout = exc.output.decode(errors="replace").strip() if isinstance(exc.output, bytes) else (exc.output or "")
+        stderr = exc.stderr.decode(errors="replace").strip() if isinstance(exc.stderr, bytes) else (exc.stderr or "")
+        timeout_msg = f"Command timed out after {exc.timeout:g}s"
         raise SubprocessCommandError(
     -            list(exc.cmd) if isinstance(exc.cmd, (list, tuple)) else [str(exc.cmd)],
-            "",
-            f"Command timed out after {exc.timeout:.0f}s",
+            list(exc.cmd) if isinstance(exc.cmd, (list, tuple)) else [str(exc.cmd)],
+            stdout,
+            f"{stderr}{os.linesep if stderr else ''}{timeout_msg}",
             124,
         ) from exc
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dfetch/util/cmdline.py` around lines 52 - 58, The TimeoutExpired handler in
run_on_cmdline currently drops captured output; change the except block to
extract exc.stdout and exc.stderr (they may be bytes or None), decode bytes to
str using errors="replace", default to "" when None, and pass those decoded
stdout/stderr into the SubprocessCommandError constructor (retain the timeout
message and exit code 124) so partial output is preserved for diagnostics;
reference run_on_cmdline and SubprocessCommandError to locate and update the
handler.

Comment thread dfetch/vcs/svn.py Outdated
Comment thread dfetch/vcs/svn.py
@spoorcc spoorcc force-pushed the svn-ssh-non-onteractive branch from d798bd0 to f6f8068 Compare June 2, 2026 20:02
@spoorcc spoorcc force-pushed the svn-ssh-non-onteractive branch from f6f8068 to ce9644c Compare June 2, 2026 20:18
@spoorcc

spoorcc commented Jun 2, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 2, 2026

Copy link
Copy Markdown
Contributor
✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/test_svn.py (1)

577-584: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Verify that existing SSH options are preserved.

The test correctly validates that BatchMode=yes is not duplicated, but it doesn't verify that other SSH options (like -i /my/key) are preserved in the result. Consider adding an assertion to ensure the full original value is maintained.

🔍 Suggested assertion
     monkeypatch.setenv("SVN_SSH", "ssh -o BatchMode=yes -i /my/key")
     _extend_env_for_non_interactive_mode.cache_clear()
     env = _extend_env_for_non_interactive_mode()
     assert env["SVN_SSH"].count("BatchMode=yes") == 1
+    assert "-i /my/key" in env["SVN_SSH"]
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_svn.py` around lines 577 - 584, The test
test_svn_ssh_env_preserves_existing_batch_mode should also assert that existing
SSH options are preserved; after calling _extend_env_for_non_interactive_mode(),
add an assertion that env["SVN_SSH"] still contains the original key option
(e.g. assert "-i /my/key" in env["SVN_SSH"] or assert
env["SVN_SSH"].startswith("ssh -o BatchMode=yes -i /my/key") to ensure the full
original value is maintained) while keeping the existing BatchMode=yes
duplication check.
dfetch/vcs/svn.py (1)

389-404: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Finish applying the centralized SSH host-key handling to the remaining remote entry points.

get_last_changed_revision() for remote targets, export(), and files_in_path() all pass BatchMode=yes, but they still surface raw SubprocessCommandError on host-key verification failures. Wrap these run_on_cmdline() calls the same way as get_info_from_target() so callers get the same remediation path everywhere.

Also applies to: 443-449, 454-460

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dfetch/vcs/svn.py` around lines 389 - 404, get_last_changed_revision(),
export(), and files_in_path() still call run_on_cmdline() directly and will
raise raw SubprocessCommandError on SSH host-key verification failures; update
each to mirror get_info_from_target() by wrapping their run_on_cmdline() calls
in the same try/except used there (catch SubprocessCommandError), invoke the
same remediation/host-key handling helper used by get_info_from_target(), and
re-run or re-raise as appropriate so callers receive the centralized remediation
path instead of raw subprocess errors; locate the run_on_cmdline invocations in
get_last_changed_revision(), export(), and files_in_path() and apply the
identical exception-handling pattern found in get_info_from_target().
♻️ Duplicate comments (4)
dfetch/vcs/svn.py (2)

48-54: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a host-only target in the ssh-keyscan hint.

_ssh_target_from_url() can return user@host, but ssh-keyscan expects the host portion. Keep user@host only for the ssh -T example.

Does `ssh-keyscan` accept targets in `user@host` form, or only hostnames/IP addresses?
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dfetch/vcs/svn.py` around lines 48 - 54, The ssh hint uses
_ssh_target_from_url(url) which may return "user@host" but ssh-keyscan requires
just the host; update the code to extract a host-only value (e.g., call or add a
helper to parse host from url or strip the user prefix from the result of
_ssh_target_from_url) and use that host-only string in the ssh-keyscan line,
while keeping the original target (user@host) for the ssh -T example; adjust the
two formatted f-strings in the RuntimeError construction around
_ssh_target_from_url(url) accordingly (use host_only for the ssh-keyscan hint
and the original target for the ssh -T hint).

238-241: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle host-key failures in externals_from_url().

This svn propget call now runs with the non-interactive SSH env, but it still skips _raise_if_ssh_host_key_error(), so missing known-hosts state leaks a raw SubprocessCommandError instead of the new remediation message.

♻️ Minimal fix
-        result = run_on_cmdline(logger, cmd, env=_extend_env_for_non_interactive_mode())
+        try:
+            result = run_on_cmdline(
+                logger,
+                cmd,
+                env=_extend_env_for_non_interactive_mode(),
+            )
+        except SubprocessCommandError as exc:
+            _raise_if_ssh_host_key_error(url, exc)
+            raise
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dfetch/vcs/svn.py` around lines 238 - 241, The svn propget call in
externals_from_url (the run_on_cmdline invocation) runs with non-interactive SSH
but doesn't check for SSH host-key failures; after capturing the subprocess
result, call SvnRepo._raise_if_ssh_host_key_error(result, url) (or the existing
helper that inspects the subprocess result) before calling
SvnRepo._normalize_url_prefix and SvnRepo._parse_externals so host-key issues
produce the remediation message instead of a raw SubprocessCommandError.
CHANGELOG.rst (1)

24-24: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Update placeholder issue reference.

The changelog entry still references (#0), which should be (#1230) based on the PR objectives. This issue was previously flagged and marked as addressed in commit 81387c8, but remains in the current code.

📝 Proposed fix
-* Run ``svn+ssh://`` connections in non-interactive mode to prevent hanging (`#0`)
+* Run ``svn+ssh://`` connections in non-interactive mode to prevent hanging (`#1230`)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@CHANGELOG.rst` at line 24, Update the placeholder issue reference in the
changelog entry that reads "* Run ``svn+ssh://`` connections in non-interactive
mode to prevent hanging (`#0`)" to use the correct issue number "(`#1230`)"; locate
the changelog line containing the phrase "Run ``svn+ssh://`` connections in
non-interactive mode to prevent hanging" and replace the "(`#0`)" token with
"(`#1230`)" so the entry reflects the proper issue reference.
example/dfetch.yaml (1)

10-10: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Verify SSH prerequisites for the svn+ssh example.

The change from svn:// to svn+ssh:// demonstrates the non-interactive SSH mode feature. However, documentation explaining SSH key registration and known-hosts setup for SourceForge was previously requested and marked as addressed in commit 81387c8, but is not visible in the current code.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@example/dfetch.yaml` at line 10, The example changed the config key value
url-base: svn+ssh://svn.code.sf.net/p/ to use non-interactive SSH but lacks
documentation on SSH prerequisites; add a short doc/README note referenced from
the example that explains registering your SSH public key with SourceForge,
adding SourceForge's host key to users' known_hosts (or using a secured ssh
config entry), and testing non-interactive SSH access (ssh -T or svn list)
before using the url-base value, and link or mention this guidance next to the
example so users of the url-base: svn+ssh://... snippet know the required SSH
setup steps.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/settings.json:
- Around line 5-31: Split the unrelated expanded Claude assistant permission
entries (e.g., "Bash(git stash *)", "Bash(pip install *)", "Bash(pytest *)",
"Bash(dfetch add *)", etc.) out of the current commit into a separate PR focused
on dev-tooling/config changes, leaving only the svn+ssh hang fix in this PR;
alternatively, if any of these entries are required to test or validate the
svn+ssh change, add a short note in the PR description explaining which specific
permission(s) (reference the exact entries above) are required and why so
reviewers understand their relevance.

In `@tests/test_svn.py`:
- Around line 542-549: Add a new parametrized test in tests/test_svn.py that
mirrors test_get_info_from_target_raises_hint_on_ssh_host_key_failure but covers
SvnRepo.externals_from_url, SvnRepo.get_last_changed_revision,
SvnRepo("...").export, and SvnRepo.files_in_path: for each method call patch
dfetch.vcs.svn.run_on_cmdline to raise SubprocessCommandError with stderr "Host
key verification failed." and assert the call raises a RuntimeError matching
"known hosts"; reference the SvnRepo class and the four method names
(externals_from_url, get_last_changed_revision, export, files_in_path) so the
test targets the updated non-interactive SSH behavior.
- Around line 525-540: The parameterized test
test_svn_remote_raises_hint_on_ssh_host_key_failure is missing coverage for
SvnRemote.ls_tree; update the param list in the pytest.mark.parametrize tuple to
include ("ls_tree", "svn+ssh://svn.code.sf.net/project") so ls_tree is exercised
the same way as is_svn, list_of_tags, and list_of_branches and will raise the
same SSH host-key hint on SubprocessCommandError.

---

Outside diff comments:
In `@dfetch/vcs/svn.py`:
- Around line 389-404: get_last_changed_revision(), export(), and
files_in_path() still call run_on_cmdline() directly and will raise raw
SubprocessCommandError on SSH host-key verification failures; update each to
mirror get_info_from_target() by wrapping their run_on_cmdline() calls in the
same try/except used there (catch SubprocessCommandError), invoke the same
remediation/host-key handling helper used by get_info_from_target(), and re-run
or re-raise as appropriate so callers receive the centralized remediation path
instead of raw subprocess errors; locate the run_on_cmdline invocations in
get_last_changed_revision(), export(), and files_in_path() and apply the
identical exception-handling pattern found in get_info_from_target().

In `@tests/test_svn.py`:
- Around line 577-584: The test test_svn_ssh_env_preserves_existing_batch_mode
should also assert that existing SSH options are preserved; after calling
_extend_env_for_non_interactive_mode(), add an assertion that env["SVN_SSH"]
still contains the original key option (e.g. assert "-i /my/key" in
env["SVN_SSH"] or assert env["SVN_SSH"].startswith("ssh -o BatchMode=yes -i
/my/key") to ensure the full original value is maintained) while keeping the
existing BatchMode=yes duplication check.

---

Duplicate comments:
In `@CHANGELOG.rst`:
- Line 24: Update the placeholder issue reference in the changelog entry that
reads "* Run ``svn+ssh://`` connections in non-interactive mode to prevent
hanging (`#0`)" to use the correct issue number "(`#1230`)"; locate the changelog
line containing the phrase "Run ``svn+ssh://`` connections in non-interactive
mode to prevent hanging" and replace the "(`#0`)" token with "(`#1230`)" so the
entry reflects the proper issue reference.

In `@dfetch/vcs/svn.py`:
- Around line 48-54: The ssh hint uses _ssh_target_from_url(url) which may
return "user@host" but ssh-keyscan requires just the host; update the code to
extract a host-only value (e.g., call or add a helper to parse host from url or
strip the user prefix from the result of _ssh_target_from_url) and use that
host-only string in the ssh-keyscan line, while keeping the original target
(user@host) for the ssh -T example; adjust the two formatted f-strings in the
RuntimeError construction around _ssh_target_from_url(url) accordingly (use
host_only for the ssh-keyscan hint and the original target for the ssh -T hint).
- Around line 238-241: The svn propget call in externals_from_url (the
run_on_cmdline invocation) runs with non-interactive SSH but doesn't check for
SSH host-key failures; after capturing the subprocess result, call
SvnRepo._raise_if_ssh_host_key_error(result, url) (or the existing helper that
inspects the subprocess result) before calling SvnRepo._normalize_url_prefix and
SvnRepo._parse_externals so host-key issues produce the remediation message
instead of a raw SubprocessCommandError.

In `@example/dfetch.yaml`:
- Line 10: The example changed the config key value url-base:
svn+ssh://svn.code.sf.net/p/ to use non-interactive SSH but lacks documentation
on SSH prerequisites; add a short doc/README note referenced from the example
that explains registering your SSH public key with SourceForge, adding
SourceForge's host key to users' known_hosts (or using a secured ssh config
entry), and testing non-interactive SSH access (ssh -T or svn list) before using
the url-base value, and link or mention this guidance next to the example so
users of the url-base: svn+ssh://... snippet know the required SSH setup steps.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: ee5c747c-98e6-4f39-abc2-48501f388bab

📥 Commits

Reviewing files that changed from the base of the PR and between d798bd0 and ce9644c.

📒 Files selected for processing (5)
  • .claude/settings.json
  • CHANGELOG.rst
  • dfetch/vcs/svn.py
  • example/dfetch.yaml
  • tests/test_svn.py

Comment thread .claude/settings.json
Comment thread tests/test_svn.py
Comment thread tests/test_svn.py
@spoorcc spoorcc force-pushed the svn-ssh-non-onteractive branch from ce9644c to 5d550c7 Compare June 2, 2026 21:05
@spoorcc spoorcc force-pushed the svn-ssh-non-onteractive branch from 5d550c7 to 1b58c1d Compare June 2, 2026 21:58

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@dfetch/vcs/svn.py`:
- Line 71: Replace the redundant str() wrapper around the decoded stdout: change
the return of "return str(result.stdout.decode())" to simply return the decoded
string (i.e., return result.stdout.decode()). This removes the unnecessary
conversion while preserving existing behavior in the function containing that
return expression.
- Around line 135-138: list_of_tags() currently calls _run_svn directly and can
raise SubprocessCommandError if the tags/ path is missing; update list_of_tags
to mirror list_of_branches by wrapping the _run_svn call in a try/except
SubprocessCommandError block (catch the exception and return an empty list),
keeping the same output parsing logic (strip "/\r" and split by "\n") and
referencing list_of_tags, list_of_branches, _run_svn, and SubprocessCommandError
to locate where to add the handler.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 57862597-74b9-4d2e-a123-1c822ff2b03b

📥 Commits

Reviewing files that changed from the base of the PR and between ce9644c and 1b58c1d.

📒 Files selected for processing (5)
  • .readthedocs.yml
  • CHANGELOG.rst
  • dfetch/vcs/svn.py
  • tests/test_svn.py
  • tests/test_svn_vcs.py

Comment thread dfetch/vcs/svn.py Outdated
Comment thread dfetch/vcs/svn.py
Comment on lines 135 to +138
def list_of_tags(self) -> list[str]:
"""Get list of all available tags."""
result = run_on_cmdline(
logger, ["svn", "ls", "--non-interactive", f"{self._remote}/tags"]
)
return [
str(tag).strip("/\r") for tag in result.stdout.decode().split("\n") if tag
]
output = _run_svn(["ls", f"{self._remote}/tags"], url=self._remote)
return [str(tag).strip("/\r") for tag in output.split("\n") if tag]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Missing exception handling inconsistent with list_of_branches().

list_of_tags() lacks the try/except block that list_of_branches() has. If the tags/ directory doesn't exist, this will raise SubprocessCommandError instead of returning an empty list. This behavioral inconsistency could break callers that expect both methods to handle missing directories gracefully.

🐛 Suggested fix to match list_of_branches() pattern
     def list_of_tags(self) -> list[str]:
         """Get list of all available tags."""
-        output = _run_svn(["ls", f"{self._remote}/tags"], url=self._remote)
-        return [str(tag).strip("/\r") for tag in output.split("\n") if tag]
+        try:
+            output = _run_svn(["ls", f"{self._remote}/tags"], url=self._remote)
+            return [str(tag).strip("/\r") for tag in output.split("\n") if tag]
+        except SshHostKeyError:
+            raise
+        except (SubprocessCommandError, RuntimeError):
+            return []
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@dfetch/vcs/svn.py` around lines 135 - 138, list_of_tags() currently calls
_run_svn directly and can raise SubprocessCommandError if the tags/ path is
missing; update list_of_tags to mirror list_of_branches by wrapping the _run_svn
call in a try/except SubprocessCommandError block (catch the exception and
return an empty list), keeping the same output parsing logic (strip "/\r" and
split by "\n") and referencing list_of_tags, list_of_branches, _run_svn, and
SubprocessCommandError to locate where to add the handler.

@spoorcc spoorcc force-pushed the svn-ssh-non-onteractive branch from 1b58c1d to 0cc082e Compare June 10, 2026 20:24
@spoorcc

spoorcc commented Jun 10, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai resume
@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

@spoorcc Sure! Resuming automatic reviews and triggering a review of this PR now.

✅ Action performed

Reviews resumed.


Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
tests/test_svn.py (1)

538-671: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add Google-style docstrings to the new test functions.

The newly added tests in this block are missing function docstrings, which violates the test-style guideline.

📝 Example pattern
 def test_ssh_hint_includes_hostname():
+    """Raises a known-hosts hint that includes the parsed SSH hostname."""
     stderr = "Host key verification failed."

As per coding guidelines, “tests/**/*.py: Docstrings in test functions should follow Google style convention.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_svn.py` around lines 538 - 671, Several new test functions lack
Google-style docstrings; add a one- to two-sentence Google-style docstring to
each test function (e.g., test_svn_remote_raises_hint_on_ssh_host_key_failure,
test_get_info_from_target_raises_hint_on_ssh_host_key_failure,
test_svn_repo_raises_hint_on_ssh_host_key_failure,
test_ssh_hint_includes_hostname,
test_ssh_hint_includes_user_when_present_in_url,
test_svn_ssh_env_has_batch_mode, test_svn_ssh_env_preserves_existing_batch_mode,
test_run_svn_passes_non_interactive_env_to_subprocess,
test_ssh_hint_on_authenticity_of_host_message,
test_ssh_hint_without_url_omits_hostname_commands,
test_browse_tree_raises_hint_on_ssh_host_key_failure,
test_create_diff_handles_non_utf8_diff_output) that describes the test purpose
and any important conditions; follow Google docstring style (short summary,
optional blank line, and brief notes on the scenario/assertions) and place it
immediately under each def line.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.claude/settings.json:
- Around line 23-24: The allowlist entries "Bash(pip install *)" and
"Bash(pytest *)" (and the similar wildcard entries around lines 28–31) are too
permissive; replace these star-based patterns with least-privilege shapes by
enumerating allowed package/flag patterns or using constrained argument regexes
instead of "*" — e.g., restrict "pip install" to specific package
names/versions, constrain "pytest" to specific test paths/flags, and tighten any
"dfetch" or other Bash(...) entries to explicit, validated argument forms so
arbitrary installs/tests/commands cannot be executed.

In @.readthedocs.yml:
- Line 22: The post_checkout step currently suppresses all errors by running
"git fetch --unshallow || true"; change it to only run git fetch --unshallow
when the repo is actually shallow (e.g., test git rev-parse
--is-shallow-repository or inspect .git/shallow) so real fetch failures are not
hidden; update the post_checkout sequence to conditionally execute "git fetch
--unshallow" when shallow, otherwise skip it, keeping the step name
(post_checkout) and the command string ("git fetch --unshallow") so reviewers
can find and verify the change.

In `@tests/test_svn.py`:
- Around line 543-544: Update the host-key failure tests to assert the specific
SshHostKeyError instead of a generic RuntimeError: replace
pytest.raises(RuntimeError, match="known hosts") with
pytest.raises(SshHostKeyError, match="known hosts") for the calls invoking
getattr(SvnRemote(url), method)(*call_args) (and apply the same change to the
other test occurrences referenced), ensuring the tests import or reference the
SshHostKeyError class used by the SvnRemote implementation.

---

Outside diff comments:
In `@tests/test_svn.py`:
- Around line 538-671: Several new test functions lack Google-style docstrings;
add a one- to two-sentence Google-style docstring to each test function (e.g.,
test_svn_remote_raises_hint_on_ssh_host_key_failure,
test_get_info_from_target_raises_hint_on_ssh_host_key_failure,
test_svn_repo_raises_hint_on_ssh_host_key_failure,
test_ssh_hint_includes_hostname,
test_ssh_hint_includes_user_when_present_in_url,
test_svn_ssh_env_has_batch_mode, test_svn_ssh_env_preserves_existing_batch_mode,
test_run_svn_passes_non_interactive_env_to_subprocess,
test_ssh_hint_on_authenticity_of_host_message,
test_ssh_hint_without_url_omits_hostname_commands,
test_browse_tree_raises_hint_on_ssh_host_key_failure,
test_create_diff_handles_non_utf8_diff_output) that describes the test purpose
and any important conditions; follow Google docstring style (short summary,
optional blank line, and brief notes on the scenario/assertions) and place it
immediately under each def line.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 52bb39b5-6274-45b5-9c87-fd00a910db43

📥 Commits

Reviewing files that changed from the base of the PR and between ce9644c and 0cc082e.

📒 Files selected for processing (8)
  • .claude/settings.json
  • .readthedocs.yml
  • CHANGELOG.rst
  • dfetch/vcs/svn.py
  • doc/howto/troubleshooting.rst
  • features/check-svn-repo.feature
  • tests/test_svn.py
  • tests/test_svn_vcs.py

Comment thread .claude/settings.json
Comment on lines +23 to +24
"Bash(pip install *)",
"Bash(pytest *)",

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Restrict wildcard Bash permissions to least privilege.

These *-based entries materially widen what the assistant can execute and weaken the allowlist boundary. For high-risk commands (pip install, broad pytest, and unconstrained dfetch args), prefer explicit command/arg shapes.

🔧 Suggested tightening example
-      "Bash(pip install *)",
-      "Bash(pytest *)",
+      "Bash(pip install -e .[docs])",
+      "Bash(pytest tests/*)",

Also applies to: 28-31

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/settings.json around lines 23 - 24, The allowlist entries "Bash(pip
install *)" and "Bash(pytest *)" (and the similar wildcard entries around lines
28–31) are too permissive; replace these star-based patterns with
least-privilege shapes by enumerating allowed package/flag patterns or using
constrained argument regexes instead of "*" — e.g., restrict "pip install" to
specific package names/versions, constrain "pytest" to specific test
paths/flags, and tighten any "dfetch" or other Bash(...) entries to explicit,
validated argument forms so arbitrary installs/tests/commands cannot be
executed.

Comment thread .readthedocs.yml Outdated
Comment thread tests/test_svn.py Outdated
@spoorcc spoorcc force-pushed the svn-ssh-non-onteractive branch from 0cc082e to 5fd2026 Compare June 10, 2026 20:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant